// Copyright (c) 1996-2002 John Lyon-Smith All rights reserved.

#pragma once

#include <wxtl\Base.h>
#include <wxtl\Trace.h>
#include <wxtl\StringUtil.h>

namespace wxtl
{
	class Environ
	{
	public:
		typedef map<tstring, tstring, less_no_case<tstring> > Vars;
		typedef Vars::const_iterator VarsConstIterator;
		typedef Vars::iterator VarsIterator;
		typedef pair<tstring, tstring> VarPair;

	protected:
		Vars m_vars;

	public:		
		Environ()
		{
		}

		~Environ()
		{
		}

		Environ(const Environ& rhs)
		{
			copy(rhs);
		}

		Environ& operator=(const Environ& rhs)
		{
			copy(rhs);			
			return *this;
		}

		void copy(const Environ& rhs)
		{
			m_vars.clear();
			
			for (VarsConstIterator i = rhs.m_vars.begin(); i != rhs.m_vars.end(); i++)
			{
				m_vars.insert(VarPair(i->first, i->second));
			}
		}

		static bool isUnicodeEnvironmentBlock(PVOID p, int cb = 32) 
		{
			int flag = IS_TEXT_UNICODE_STATISTICS;
			
			return ::IsTextUnicode(p, cb, &flag) ? true : false;
		}

		// Only use this when you have no idea what you've been handed
		// Otherwise use CopyFromBlock<T> with T=CHAR or T=WCHAR
		void copyFromBlock(PVOID p, bool ignoreHidden = true)
		{
			if (isUnicodeEnvironmentBlock(p))
				copyFromBlock((char*)p, ignoreHidden);
			else
				copyFromBlock((wchar_t*)p, ignoreHidden);
		}

	#pragma warning(push)
	#pragma warning(disable: 4127)  // conditional expression is constant

		//NOTE: We assume that any environment from the O/S has all the variables in upper case!
		template<class T>
		void copyFromBlock(const T* p, bool ignoreHidden = true) throw()
		{
			if (m_vars.size() > 0)
				m_vars.clear();
	
			const T* psz = p;

			while (*psz)
			{
				tstring line;
				
			#ifdef _UNICODE
				if (sizeof(T) == sizeof(wchar_t))
					line = (const wchar_t*)psz;
				else
					line = asciiToUnicode((const char*)psz);
			#else
				if (sizeof(T) == sizeof(wchar_t))
					line = unicodeToAscii((const wchar_t*)psz);
				else
					line = (const char*)psz;
			#endif
				
				size_t pos = line.find('=');

				_ASSERTE(pos != tstring::npos);

				if (pos > 0 || (pos == 0 && !ignoreHidden))
				{
					// Assume the block doesn't have any unexpanded variables
					tstring var(line, 0, pos);
					tstring val(line, pos + 1);
					
					m_vars.insert(Vars::value_type(var, val));
				}
				
				// Advance to next variable
				while (*psz++);
			}
		}

		/*template void copyFromBlock<char>(const char*, bool);
		template void copyFromBlock<wchar_t>(const wchar_t*, bool);*/

	#pragma warning(pop)

		void copyFromCurrentProcess(bool ignoreHidden = true)
		{
			m_vars.clear();
	
			LPVOID pBlock = GetEnvironmentStrings();

			copyFromBlock((TCHAR*)pBlock, ignoreHidden);

			FreeEnvironmentStrings((LPTSTR)pBlock);
		}

		template <class T>
		void createBlock(shared_array<T>& pBlock)
		{
			size_t size = sizeof(T);  // '\0'

			// Calculate size of the block	
			VarsConstIterator i = m_vars.begin();
			for (; i != m_vars.end(); i++)
			{
				// ignore any environment vars that are empty
				if (!i->second.empty())
				{
					size += 
						i->first.size() + sizeof(T) /* '=' */ +
						i->second.size() + sizeof(T) /* '\0' */;
				}
			}

			pBlock = shared_array<T>(new T[size]);

			T* pch = pBlock.get();

			for (i = m_vars.begin(); i != m_vars.end(); i++)
			{
				// ignore any environment vars that are empty
				if (!i->second.empty())
				{
					const TCHAR* pchT = i->first.c_str();

					while(*pchT)
						*(pch++) = (T)*(pchT++);

					*(pch++) = '=';
					pchT = i->second.c_str();

					while(*pchT)
						*(pch++) = (T)*(pchT++);
					
					*(pch++) = '\0';
				}
			}
			*pch++ = '\0';
		}

		bool set(
			const TCHAR* pszName, 
			const TCHAR* pszValue, 
			bool bExpand = true, 
			bool bRemoveUnfound = false)
		{
			if (pszName == NULL || *pszName == 0)
				return false;

			tstring strName(pszName);
			tstring strValue(pszValue);
			
			if (bExpand)
				expand(strValue, bRemoveUnfound);

			strName = toUpper(strName.c_str());
			
			m_vars.insert(VarPair(strName.c_str(), strValue));

			return true;
		}

		void erase(const TCHAR* pszName)
		{
			m_vars.erase(pszName);
		}

		void clear()
		{
			m_vars.clear();
		}

		bool get(const TCHAR* pszName, tstring& strValue) const
		{ 
			VarsConstIterator i = find(tstring(pszName));
			
			if (i == end())
			{
				strValue.clear();
				return false;
			}
			else
			{
				strValue = i->second;
				return true;
			}
		}

		bool exists(const TCHAR* pszName)
		{
			VarsConstIterator i = find(tstring(pszName));
			return i != end();
		}

		void expand(tstring& strValue, bool bRemoveUnfound = false) const
		{
			tstring strNew;
			size_t iBase = 0;
			size_t iLen = strValue.size();
				
			for (size_t i = iBase; i < iLen; i++)
			{
				if (strValue[i] != '%')
					continue;
				
				strNew += strValue.substr(iBase, i - iBase);

				iBase = i;
				i++;

				while (i < iLen && strValue[i] != '%')
					i++;

				if (i == iLen)
					break;

				tstring str;
				
				str = strValue.substr(iBase + 1, i - iBase - 1);
				
				str = trim(str);

				i++;		

				VarsConstIterator iter;
				if (!str.empty() && (iter = find(str)) != end())
					strNew += iter->second;
				else if (!bRemoveUnfound)
					strNew += strValue.substr(iBase, i - iBase);

				iBase = i;
			}	

			strNew += strValue.substr(iBase);

			strValue = strNew;
		}

		void merge(const Environ& env, bool bOverwrite = false)
		{
			for (VarsConstIterator i = env.begin(); i != env.end(); i++)
			{
				if (bOverwrite || (!bOverwrite && !exists(i->first.c_str())))
					m_vars.insert(VarPair(i->first, i->second));
			}
		}

		// Remove all variables with an empty value a la MS-DOS
		void removeEmpties()
		{
			for (VarsIterator i = m_vars.begin(); i != m_vars.end(); i++)
			{
				if (i->second.empty())
				{
					m_vars.erase(i++);
				}
			}
		}

		size_t size()
		{
			return m_vars.size();
		}

		VarsConstIterator begin() const
		{
			return m_vars.begin();
		}
		
		VarsConstIterator end() const
		{
			return m_vars.end();
		}

		void erase(VarsIterator i)
		{
			m_vars.erase(i);
		}
		
		VarsConstIterator find(const tstring& s) const
		{
			return m_vars.find(s);
		} 

#ifdef _DEBUG
		void dump()
		{
			for (VarsConstIterator i = m_vars.begin(); i != m_vars.end(); i++)
			{
				WXTRACE(g_isUnicode ? "%S=%S\n" : "%s=%s\n", 
					i->first.c_str(), i->second.c_str());
			}
		}
#endif
	};
	template void Environ::copyFromBlock<char>(const char*, bool);
	template void Environ::copyFromBlock<wchar_t>(const wchar_t*, bool);
}
